/* @flow */

import Dep from './dep'
import { arrayMethods } from './array'
import {
  def,
  warn,
  hasOwn,
  hasProto,
  isObject,
  isPlainObject,
  isValidArrayIndex,
  isServerRendering
} from '../util/index'

//arrayMethods变量是一个对象，该对象中存放着数组原生的方法名以及对应的重写的数组方法
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

/**
 * By default, when a reactive property is set, the new value is
 * also converted to become reactive. However when passing down props,
 * we don't want to force conversion because the value may be a nested value
 * under a frozen data structure. Converting it would defeat the optimization.
 */
export const observerState = {
  shouldConvert: true
}

/**
 * Observer class that are attached to each observed
 * object. Once attached, the observer converts target
 * object's property keys into getter/setters that
 * collect dependencies and dispatches updates.
 */
export class Observer {
  value: any; //被观察的对象
  dep: Dep;
  /**
   * 该属性用于记录某个对象作为vue实例的$data对象的次数，
   * 示例：
   * let dataVal = {hello:'world'};
   * Vue.component('one', {
   *    data(){
   *        return dataVal;
   *    }
   * });
   * 如one组件的定义所示，这是一个错误的使用场景，data函数中返回同一个对象，所以在
   * 组件中创建多个one组件的实例时，比如创建了三个实例，则dataVal.__ob__.vmCount为3。
   * 该属性的作用：
   * 目前看来，该属性主要用于标记data对象，这样当vue框架的使用者想通过set方法向vm.$data对象上动态设置值时，
   * 可以发出错误警告，因为不可以动态的向vm实例或者vm.$data对象动态的新增属性。
   */
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    //在目标对象上添加__ob__属性指向当前观察者，首先需要知道在观察者的dep中
    //存储的监听者对象有哪些：是目标对象作为属性值时，对应的键上添加的监听者对象，
    //比如：{a:{b:'c'}}，其中{b:'c'}是目标对象，该对象添加__ob__属性，该属性的dep中
    //存储着对于键a的监听对象，详情可查看defineReactive方法的源码。
    //添加__ob__的目的是：
    //1）在目标对象上新增或者删除属性时，可以触发dep中存储的监听者对象，至于这些监听者对象是哪些，上面
    //已经解释过了。
    //2）如果目标对象为数组，则数组中数据的增删也是通过__ob__属性指向的观察者进行发布的。
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      const augment = hasProto
        ? protoAugment
        : copyAugment
      //调用augment方法可为value添加监听数组方法调用的功能，比如数组的push,splice等方法的调用
      //都可以被监听到
      augment(value, arrayMethods, arrayKeys)
      //为数组中的每个对象添加数据响应系统
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

  /**
   * Walk through each property and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]])
    }
  }

  /**
   * Observe a list of Array items.
   * 为数组中的每个数据添加监听
   */
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

// helpers

/**
 * Augment an target Object or Array by intercepting
 * the prototype chain using __proto__
 */
function protoAugment (target, src: Object, keys: any) {
  /* eslint-disable no-proto */
  //在数组对象与Array.prototype之间添加一个代理对象，用于监听数组方法的对数组的处理。
  target.__proto__ = src
  /* eslint-enable no-proto */
}

/**
 * Augment an target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  //如果不可访问数组对象的__proto__属性，则只能在数组对象上设置相应的检测方法
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 * 为目标对象创建一个观察者实例，如果成功，则返回一个新的观察者实例，或者
 * 如果已经存在，则返回已存在的观察者实例。
 * asRootData的作用是什么？
 * 答：用于标识是否在处理vue的data选项，并且在Observer对象上修改vmCount的值
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value)) {
    return
  }
  let ob: Observer | void
  //__ob__属性可能存在的情况之一就是该对象是从父组件传递过来的，该对象在父组件中已经创建了数据响应系统
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {// 如果已经存在观察者实例
    ob = value.__ob__
  } else if (
    observerState.shouldConvert &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

/**
 * Define a reactive property on an Object.
 * 真正的为一个对象定义响应数据系统的方法
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  //该参数说明不观察其val对象的内部数据，也不需要获得val对象可能存在的__ob__属性对应的被观察者对象
  shallow?: boolean
) {
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set

  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) { //在get中收集监听者
        dep.depend()
        //如果子对象的观察者存在，则将监听对象添加到子对象的观察者对象中
        if (childOb) {
          childOb.dep.depend()
        }
        if (Array.isArray(value)) {
          //如果value是数组，则数组中的每个成员也会依次循环将监听对象添加到该成员的观察者对象中，
          //注意，只是为数组中的对象的__ob__添加watcher，而不会为内部属性添加。
          dependArray(value)
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      //如果newVal和value相等的话，就不会调用相应的watcher
      //newVal !== newVal && value !== value说明newVal和value都是NaN
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      //对newVal设置数据响应系统，并覆盖childOb变量
      childOb = !shallow && observe(newVal)
      //在setter中运行所有监听者，注意，调用dep.notify方法后，最终会调用watcher.get方法，
      //该方法会将watcher置于栈顶，这样就会调用
      //if (childOb) {
      //   childOb.dep.depend()
      //}
      //所以通过setter设置的newVal的对象对应的childOb中也会存在所有的监听对象，因为所有dep变量中的
      //监听对象又重新添加了一次，也就再次添加到了childOb对象中
      dep.notify()
    }
  })
}

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set (target: Array<any> | Object, key: any, val: any): any {
  //如果target是数组，则无论该数组是否已经添加响应式，都直接为该数组设置值，
  //如果是响应式的，可直接检测到改变。
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    //如果target已经添加了数据响应系统，则splice方法已经被覆盖，已经可以触发数据响应系统
    target.splice(key, 1, val)
    return val
  }
  if (hasOwn(target, key)) {
    //如果target已经添加了数据响应系统，则直接赋值也会触发defineReactive方法中的setter，
    //相关信息可查看defineReactive方法中的注释
    target[key] = val
    return val
  }

  //获得当前对象的观察者对象
  const ob = (target: any).__ob__
  //target._isVue如果为true则说明target是vue实例对象
  //ob && ob.vmCount为true则说明target是vue实例中的data属性的值
  //总结来说就是不可为vue实例或者data属性的值添加属性，官方文档中已经说的很清楚了
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  //target是普通对象
  if (!ob) {
    target[key] = val
    return val
  }
  //ob.value就是指向target对象
  defineReactive(ob.value, key, val)
  //这里就是为对象添加__ob__属性的原因之一，为了在新增属性时调用相应的监听
  ob.dep.notify()
  return val
}

/**
 * Delete a property and trigger change if necessary.
 */
export function del (target: Array<any> | Object, key: any) {
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    //见set方法中的注释
    target.splice(key, 1)
    return
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid deleting properties on a Vue instance or its root $data ' +
      '- just set it to null.'
    )
    return
  }
  if (!hasOwn(target, key)) {
    return
  }
  delete target[key]
  if (!ob) {
    return
  }
  //这里就是为对象添加__ob__属性的原因之一，为了在删除属性时调用相应的监听
  ob.dep.notify()
}

/**
 * Collect dependencies on array elements when the array is touched, since
 * we cannot intercept array element access like property getters.
 * 示例：
 * {
 *    data:{
 *      hello:[{world:'go', man:1}, {one:'one', two:'two'}]
 *    },
 *    watch:{
 *      hello:function(){
 *
 *      }
 *    }
 * }
 * 此时对于hello的监听，在通过set或delete方法操作数组的内部数据时，也会触发相应的监听，比如
 * vm.$set(vm.hello[0], 'b', 1)，此时也会触发hello的监听函数，并传入vm.hello数组
 */
function dependArray (value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    //为什么在此处e对象已经存在__ob__属性？
    //因为在defineReactive方法中调用了let childOb = !shallow && observe(val)语句，
    //如果该语句运行（也就是shallow不为true），则val数组中的所有内部数据已经被添加了数据响应系统，所以
    //这些内部数据就是此处的一次一次循环的e对象。
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
      dependArray(e)
    }
  }
}
